#include "nuklear.h"
#include "nuklear_internal.h"

/* ===============================================================
 *
 *                              MATH
 *
 * ===============================================================*/
/*  Since nuklear is supposed to work on all systems providing floating point
    math without any dependencies I also had to implement my own math functions
    for sqrt, sin and cos. Since the actual highly accurate implementations for
    the standard library functions are quite complex and I do not need high
    precision for my use cases I use approximations.

    Sqrt
    ----
    For square root nuklear uses the famous fast inverse square root:
    https://en.wikipedia.org/wiki/Fast_inverse_square_root with
    slightly tweaked magic constant. While on today's hardware it is
    probably not faster it is still fast and accurate enough for
    nuklear's use cases. IMPORTANT: this requires float format IEEE 754

    Sine/Cosine
    -----------
    All constants inside both function are generated Remez's minimax
    approximations for value range 0...2*PI. The reason why I decided to
    approximate exactly that range is that nuklear only needs sine and
    cosine to generate circles which only requires that exact range.
    In addition I used Remez instead of Taylor for additional precision:
    www.lolengine.net/blog/2011/12/21/better-function-approximations.

    The tool I used to generate constants for both sine and cosine
    (it can actually approximate a lot more functions) can be
    found here: www.lolengine.net/wiki/oss/lolremez
*/
NK_LIB float
nk_inv_sqrt(float n) {
  float x2;
  const float threehalfs = 1.5f;
  union {
    nk_uint i;
    float f;
  } conv = {0};
  conv.f = n;
  x2 = n * 0.5f;
  conv.i = 0x5f375A84 - (conv.i >> 1);
  conv.f = conv.f * (threehalfs - (x2 * conv.f * conv.f));
  return conv.f;
}
#ifdef NK_SIN_IMPLEMENTATION
NK_LIB float
nk_sin(float x) {
  NK_STORAGE const float a0 = +1.91059300966915117e-31f;
  NK_STORAGE const float a1 = +1.00086760103908896f;
  NK_STORAGE const float a2 = -1.21276126894734565e-2f;
  NK_STORAGE const float a3 = -1.38078780785773762e-1f;
  NK_STORAGE const float a4 = -2.67353392911981221e-2f;
  NK_STORAGE const float a5 = +2.08026600266304389e-2f;
  NK_STORAGE const float a6 = -3.03996055049204407e-3f;
  NK_STORAGE const float a7 = +1.38235642404333740e-4f;
  return a0 + x * (a1 + x * (a2 + x * (a3 + x * (a4 + x * (a5 + x * (a6 + x * a7))))));
}
#endif
#ifdef NK_COS_IMPLEMENTATION
NK_LIB float
nk_cos(float x) {
  /* New implementation. Also generated using lolremez. */
  /* Old version significantly deviated from expected results. */
  NK_STORAGE const float a0 = 9.9995999154986614e-1f;
  NK_STORAGE const float a1 = 1.2548995793001028e-3f;
  NK_STORAGE const float a2 = -5.0648546280678015e-1f;
  NK_STORAGE const float a3 = 1.2942246466519995e-2f;
  NK_STORAGE const float a4 = 2.8668384702547972e-2f;
  NK_STORAGE const float a5 = 7.3726485210586547e-3f;
  NK_STORAGE const float a6 = -3.8510875386947414e-3f;
  NK_STORAGE const float a7 = 4.7196604604366623e-4f;
  NK_STORAGE const float a8 = -1.8776444013090451e-5f;
  return a0 + x * (a1 + x * (a2 + x * (a3 + x * (a4 + x * (a5 + x * (a6 + x * (a7 + x * a8)))))));
}
#endif
NK_LIB nk_uint
nk_round_up_pow2(nk_uint v) {
  v--;
  v |= v >> 1;
  v |= v >> 2;
  v |= v >> 4;
  v |= v >> 8;
  v |= v >> 16;
  v++;
  return v;
}
NK_LIB double
nk_pow(double x, int n) {
  /*  check the sign of n */
  double r = 1;
  int plus = n >= 0;
  n = (plus) ? n : -n;
  while (n > 0) {
    if ((n & 1) == 1)
      r *= x;
    n /= 2;
    x *= x;
  }
  return plus ? r : 1.0 / r;
}
NK_LIB int
nk_ifloord(double x) {
  x = (double)((int)x - ((x < 0.0) ? 1 : 0));
  return (int)x;
}
NK_LIB int
nk_ifloorf(float x) {
  x = (float)((int)x - ((x < 0.0f) ? 1 : 0));
  return (int)x;
}
NK_LIB int
nk_iceilf(float x) {
  if (x >= 0) {
    int i = (int)x;
    return (x > i) ? i + 1 : i;
  } else {
    int t = (int)x;
    float r = x - (float)t;
    return (r > 0.0f) ? t + 1 : t;
  }
}
NK_LIB int
nk_log10(double n) {
  int neg;
  int ret;
  int exp = 0;

  neg = (n < 0) ? 1 : 0;
  ret = (neg) ? (int)-n : (int)n;
  while ((ret / 10) > 0) {
    ret /= 10;
    exp++;
  }
  if (neg)
    exp = -exp;
  return exp;
}
NK_API nk_rect
nk_get_null_rect(void) {
  return nk_null_rect;
}
NK_API nk_rect
nk_make_rect(float x, float y, float w, float h) {
  nk_rect r;
  r.x = x;
  r.y = y;
  r.w = w;
  r.h = h;
  return r;
}
NK_API nk_rect
nk_make_recti(int x, int y, int w, int h) {
  nk_rect r;
  r.x = (float)x;
  r.y = (float)y;
  r.w = (float)w;
  r.h = (float)h;
  return r;
}
NK_API nk_rect
nk_recta(nk_vec2 pos, nk_vec2 size) {
  return nk_make_rect(pos.x, pos.y, size.x, size.y);
}
NK_API nk_rect
nk_rectv(const float* r) {
  return nk_make_rect(r[0], r[1], r[2], r[3]);
}
NK_API nk_rect
nk_rectiv(const int* r) {
  return nk_make_recti(r[0], r[1], r[2], r[3]);
}
NK_API nk_vec2
nk_rect_pos(nk_rect r) {
  nk_vec2 ret;
  ret.x = r.x;
  ret.y = r.y;
  return ret;
}
NK_API nk_vec2
nk_rect_size(nk_rect r) {
  nk_vec2 ret;
  ret.x = r.w;
  ret.y = r.h;
  return ret;
}
NK_LIB nk_rect
nk_shrink_make_rect(nk_rect r, float amount) {
  nk_rect res;
  r.w = NK_MAX(r.w, 2 * amount);
  r.h = NK_MAX(r.h, 2 * amount);
  res.x = r.x + amount;
  res.y = r.y + amount;
  res.w = r.w - 2 * amount;
  res.h = r.h - 2 * amount;
  return res;
}
NK_LIB nk_rect nk_pad_rect(nk_rect r, nk_vec2 pad) {
  r.w = NK_MAX(r.w, 2 * pad.x);
  r.h = NK_MAX(r.h, 2 * pad.y);
  r.x += pad.x;
  r.y += pad.y;
  r.w -= 2 * pad.x;
  r.h -= 2 * pad.y;
  return r;
}
NK_API nk_vec2 nk_make_vec2(float x, float y) {
  nk_vec2 ret;
  ret.x = x;
  ret.y = y;
  return ret;
}
NK_API nk_vec2 nk_make_vec2i(int x, int y) {
  nk_vec2 ret;
  ret.x = (float)x;
  ret.y = (float)y;
  return ret;
}
NK_API nk_vec2 nk_vec2v(const float* v) {
  return nk_make_vec2(v[0], v[1]);
}
NK_API nk_vec2 nk_vec2iv(const int* v) {
  return nk_make_vec2i(v[0], v[1]);
}
NK_LIB void nk_unify(nk_rect* clip, const nk_rect* a, float x0, float y0,
                     float x1, float y1) {
  NK_ASSERT(a);
  NK_ASSERT(clip);
  clip->x = NK_MAX(a->x, x0);
  clip->y = NK_MAX(a->y, y0);
  clip->w = NK_MIN(a->x + a->w, x1) - clip->x;
  clip->h = NK_MIN(a->y + a->h, y1) - clip->y;
  clip->w = NK_MAX(0, clip->w);
  clip->h = NK_MAX(0, clip->h);
}

NK_API void nk_triangle_from_direction(nk_vec2* result, nk_rect r,
                                       float pad_x, float pad_y, nk_heading direction) {
  float w_half, h_half;
  NK_ASSERT(result);

  r.w = NK_MAX(2 * pad_x, r.w);
  r.h = NK_MAX(2 * pad_y, r.h);
  r.w = r.w - 2 * pad_x;
  r.h = r.h - 2 * pad_y;

  r.x = r.x + pad_x;
  r.y = r.y + pad_y;

  w_half = r.w / 2.0f;
  h_half = r.h / 2.0f;

  if (direction == NK_UP) {
    result[0] = nk_make_vec2(r.x + w_half, r.y);
    result[1] = nk_make_vec2(r.x + r.w, r.y + r.h);
    result[2] = nk_make_vec2(r.x, r.y + r.h);
  } else if (direction == NK_RIGHT) {
    result[0] = nk_make_vec2(r.x, r.y);
    result[1] = nk_make_vec2(r.x + r.w, r.y + h_half);
    result[2] = nk_make_vec2(r.x, r.y + r.h);
  } else if (direction == NK_DOWN) {
    result[0] = nk_make_vec2(r.x, r.y);
    result[1] = nk_make_vec2(r.x + r.w, r.y);
    result[2] = nk_make_vec2(r.x + w_half, r.y + r.h);
  } else {
    result[0] = nk_make_vec2(r.x, r.y + h_half);
    result[1] = nk_make_vec2(r.x + r.w, r.y);
    result[2] = nk_make_vec2(r.x + r.w, r.y + r.h);
  }
}
